package ggdice

import (
	"encoding/json"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gloader"
	"wzgames/internal/gvalidator"
	"wzgames/internal/model/gamep"
	"wzgames/internal/model/gkindp"
	"wzgames/internal/model/mcomm"
	"wzgames/internal/model/statement"
	"wzgames/internal/response"
	"wzgames/internal/secure/rng"
	"wzgames/internal/services/games"
	"wzgames/pkg/logger"
	"wzgames/pkg/utils/byteutil"
	"wzgames/pkg/utils/calcutil"
	"wzgames/pkg/utils/dateutil"
	"wzgames/pkg/utils/stringutil"
)

type GDice struct {
	games.GGame

	zn   string
	cfg  *gkindp.DiceConfig
	gift *Gift
}

func NewDice(inst *gamep.GameInst) (games.IGameService, error) {
	dice := &GDice{
		gift: &Gift{},
	}
	dice.zn = "dice"
	dice.SetGameInst(inst)
	err := dice.updateConf()
	if err != nil {
		//logger.Error("err == ", zap.String("error", err.Error()))
		return dice, err
	}
	dice.gift.Init(dice.cfg)
	return dice, nil
}

func init() {
	games.RegisterGame(102, NewDice)
	games.RegisterMarker(102, NewDiceMarker)
}

func (a *GDice) updateConf() error {
	a.Lock()
	defer a.Unlock()
	if a.cfg == nil {
		a.cfg = &gkindp.DiceConfig{}
	}
	err := proto.Unmarshal(a.GameInst.Conf, a.cfg)
	if err != nil {
		logger.Error("[Dice] update Conf on proto.Unmarshal", zap.Error(err))
		return err
	}
	return nil
}

func (a *GDice) Update() {
	err := a.updateConf()
	if err == nil {
		a.gift.Update(a.cfg)
	}
}

func (a *GDice) Destroy() {

}

func (a *GDice) Login(tb *games.GameTable) {
	seed, nextSeed := rng.BeginSeed(tb.Player.AgtID(), tb.Player.ID())
	tb.Marker.InitServerSeed(seed, nextSeed)

	code := tb.InitBalance()
	msg := response.AcquireMsgO()
	msg.Id = 1
	msg.ControlId = 0
	xw := sfs.XWriter{}
	defer xw.Reset()
	if code != 0 {
		xw.WriteUShort(uint16(2))
		xw.WriteShortS(sfs.KeyErrorCode, code)
		xw.WriteUtfStringArray(sfs.KeyErrorParams, []string{})
	} else {
		xw.WriteUShort(uint16(6))
		xw.WriteShortS(sfs.KeyReconnectionSeconds, 0)
		xw.WriteStringS(sfs.KeyZoneName, a.zn)
		xw.WriteLongS(sfs.KeyId, int64(tb.Player.ID()))
		xw.WriteStringS(sfs.KeyUserName, tb.Player.ShowName())
		xw.WriteShortS(sfs.KeyPrivilegeId, 0)
		xw.WriteSfsArrayPre(sfs.KeyRoomList, 0)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
	a.clientInit(tb)
}

func (a *GDice) clientInit(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.KeyInit)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(6))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteIntS(constant.KeyValType, int32(tb.GameCurr.VType))

	xw.WriteSfsObjectPre(constant.KeyUser, uint16(3))
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteStringS(constant.KeyAvatar, tb.Player.UHead())
	xw.WriteStringS(constant.KeyUsername, tb.Player.ShowName())

	xw.WriteSfsArrayPre(constant.KeyFreeBets, uint16(0))

	xw.Write(a.cfg.GetConfigBin())
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteIntS(constant.KeyBetPrecision, tb.Seat.Precision)

	if tb.GameCurr.VType == mcomm.ValueType_Float64Type {
		xw.WriteDoubleArrayS(constant.KeyFastBets, tb.GameCurr.FloatValueLimit.FastBets)
		xw.WriteDoubleS(constant.KeyMaxBet, tb.GameCurr.FloatValueLimit.MaxBet)
		xw.WriteDoubleS(constant.KeyMinBet, tb.GameCurr.FloatValueLimit.MinBet)
		xw.WriteDoubleS(constant.KeyDefaultBetValue, tb.GameCurr.FloatValueLimit.DefaultBetValue)
		xw.WriteDoubleS(constant.KeyMaxUserWin, tb.GameCurr.FloatValueLimit.MaxUserWin)
	} else {
		xw.WriteUtfStringArray(constant.KeyFastBets, tb.GameCurr.StrValueLimit.FastBets)
		xw.WriteStringS(constant.KeyMaxBet, tb.GameCurr.StrValueLimit.MaxBet)
		xw.WriteStringS(constant.KeyMinBet, tb.GameCurr.StrValueLimit.MinBet)
		xw.WriteStringS(constant.KeyDefaultBetValue, tb.GameCurr.StrValueLimit.DefaultBetValue)
		xw.WriteStringS(constant.KeyMaxUserWin, tb.GameCurr.StrValueLimit.MaxUserWin)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GDice) Logout(tb *games.GameTable) {

}

func (a *GDice) DoMessage(tb *games.GameTable, data interface{}) {
	switch m := data.(type) {
	case *sfs.TSfsObject:
		code, cmd := a.CheckMessage(m, tb.Player.AgtID(), tb.InstID(), tb.Player.ID())
		if code != 0 {
			return
		}
		switch cmd {
		case constant.HandlerBet:
			a.betHandler(tb, m)
		case constant.HandlerServerSeed:
			a.ServerSeedHandler(tb)
		case constant.HandlerGetHistory:
			a.getBetHistory(tb, m)
		case constant.HandlerRoundDetails:
			a.getRoundDetails(tb, m)
		default:
			logger.Warn("[Dice] invalid cmd key", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()), zap.String("CmdKey", cmd))
		}
	default:
		logger.Warn("[Dice] unknown message on DoMessage", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()))
	}
}

func (a *GDice) ServerSeedHandler(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseServerSeed)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(2))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteStringS(constant.KeyServerSeed, tb.Marker.NextServerSeed256())

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GDice) verifyChance(chance float64) bool {
	if chance < a.cfg.GameConfig.RollMin || chance > a.cfg.GameConfig.RollMax {
		return false
	}
	return true
}

func (a *GDice) betHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	data := exMsg.GetContent()
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	clientSeed := data.GetUtfString(constant.KeyClientSeed)
	if !gvalidator.IsAlphaNumeric(clientSeed) || len(clientSeed) > 32 {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	betValue := data.GetUtfString(constant.KeyBet)
	if !gvalidator.IsAmount(betValue) || !gloader.CommMgr().CheckAmountPointByCurrency(tb.Seat.Currency, betValue) {
		a.RetErr(tb, response.ErrAmount, constant.ResponseBet)
		return
	}
	chanceStr := data.GetUtfString(constant.KeyNumber)
	if !gvalidator.IsAmount(chanceStr) {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	chance, _ := stringutil.ToFloat64(chanceStr, 0)
	if !a.verifyChance(chance) {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	betType := data.GetUtfString(constant.KeyType)
	if betType != constant.KeyUnder && betType != constant.KeyOver {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}

	betObj := tb.GenBetObj(mcomm.GameAction_GameImResult)

	balaCode := tb.CheckBetValue(betValue)
	if balaCode != 0 {
		if balaCode == response.ErrInsufficientBalance {
			balaCode = tb.LoadBalance()
		}
		if balaCode == 0 {
			balaCode = tb.CheckBetValue(betValue)
		}
		if balaCode != 0 {
			betObj.CkCode = int32(balaCode)
			betObj.Balance = tb.BalanceStr()
			tb.PlayInputData(betObj, exMsg)
			a.RetErrWithBala(tb, balaCode, constant.ResponseBet, true)
			return
		}
	}
	tb.PlayInputData(betObj, exMsg)

	betId := tb.GenBetNo()
	if betId == 0 {
		a.RetErr(tb, response.ErrInternal, constant.ResponseBet)
		return
	}
	tb.UpBetObj(betObj)
	result := a.gift.Calculate(tb.Marker.ServerSeed(), clientSeed)
	betObj.BetId = betId
	betObj.BetAmt = betValue
	betObj.EffAmt = betObj.BetAmt
	betObj.FreeBet = false
	diceBet := &statement.DiceBetInfo{
		ServerSeed: tb.Marker.ServerSeed(),
		ClientSeed: clientSeed,
		BetType:    betType,
		Chance:     chance,
		Result:     result,
	}
	a.calcResult(tb, betObj, diceBet)
	nextSeed := rng.NextSeed(0, 0, 0)
	tb.Marker.RefreshSeed(nextSeed)
	b, err := json.Marshal(diceBet)
	if err != nil {
		logger.Error("[Dice] bet fail on json.Marshal dice bet", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()), zap.Error(err))
		a.RetErr(tb, response.ErrInternal, constant.ResponseBet)
		return
	}
	betObj.Detail = byteutil.SliceByteToString(b)
	code := tb.ImResultBet(betObj)
	if code != 0 {
		a.RetErrWithBala(tb, code, constant.ResponseBet, true)
		return
	}
	a.betResponse(tb, betObj, diceBet)
}

func (a *GDice) calcResult(tb *games.GameTable, betObj *statement.PlayBet, diceBet *statement.DiceBetInfo) {
	win := false
	var baseN float64
	if diceBet.BetType == constant.KeyUnder {
		if diceBet.Chance > diceBet.Result {
			win = true
		}
		baseN = diceBet.Chance
	} else {
		upbase := calcutil.CalcFloatSub(99.99, diceBet.Chance)
		if upbase < diceBet.Result {
			win = true
		}
		baseN = diceBet.Chance
	}
	imResult := &statement.PlayPayout{
		BetId:   betObj.BetId,
		GameAct: betObj.GameAct,
		Win:     mcomm.GameResult_GameLose,
		GameId:  betObj.GameId,
	}

	var winAmt = "0"
	if win {
		px := decimal.NewFromFloat(100.0 - a.cfg.HouseEdge).Div(decimal.NewFromFloat(baseN))
		multiplier, _ := px.Float64()
		if multiplier < a.cfg.GameConfig.PayoutMin {
			multiplier = a.cfg.GameConfig.PayoutMin
			px = decimal.NewFromFloat(multiplier)
		}
		if multiplier > a.cfg.GameConfig.PayoutMax {
			multiplier = a.cfg.GameConfig.PayoutMax
			px = decimal.NewFromFloat(multiplier)
		}
		imResult.Win = calcutil.CheckGameResult(px)
		diceBet.Multiplier = calcutil.RoundFloorD(px)
		imResult.Payout = px.String()
		d1, _ := decimal.NewFromString(betObj.BetAmt)
		winAmt = d1.Mul(px).Truncate(tb.Seat.Precision).String()
		winAmt, _ = tb.CheckMaxWin(winAmt)
	}
	imResult.WinAmt = winAmt
	imResult.Dateline = dateutil.UnixMilli()
	betObj.ImRet = imResult
}

func (a *GDice) betResponse(tb *games.GameTable, betObj *statement.PlayBet, diceBet *statement.DiceBetInfo) {
	betAmt, _ := stringutil.ToFloat64(betObj.BetAmt, 0)
	winAmt, _ := stringutil.ToFloat64(betObj.ImRet.WinAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseBet)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(9))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteBoolS(constant.KeyWin, response.IfWin(betObj.ImRet.Win))
	xw.WriteDoubleS(constant.KeyBetAmount, betAmt)
	xw.WriteDoubleS(constant.KeyBetNumber, diceBet.Chance)
	xw.WriteDoubleS(constant.KeyDiceNumber, diceBet.Result)
	xw.WriteStringS(constant.KeyBetType, diceBet.BetType)
	xw.WriteDoubleS(constant.KeyWinAmount, winAmt)

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GDice) getBetHistory(tb *games.GameTable, m *sfs.TSfsObject) {
	limit, offset := a.GetPageLimit(m)
	if limit == 0 {
		return
	}

	tb.GetUserBets(offset, limit)
}

func (a *GDice) getRoundDetails(tb *games.GameTable, m *sfs.TSfsObject) {
	data := m.GetContent()
	if data == nil {
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		return
	}
	betId, _ := stringutil.ToInt64(data.GetUtfString(constant.KeyRoundId), 0)
	if betId <= 0 {
		return
	}

	ret, code := tb.GetUserBetDetail(betId)
	if code != 0 {
		return
	}

	diceBet := &statement.DiceBetInfo{}
	err := json.Unmarshal(byteutil.StringToSliceByte(ret.Detail), diceBet)
	if err != nil {
		logger.Error("[Dice] bet fail on json.Unmarshal dice bet", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()), zap.Int64("BetId", betId), zap.Error(err))
		return
	}

	betAmt, _ := stringutil.ToFloat64(ret.BetAmt, 0)
	winAmt, _ := stringutil.ToFloat64(ret.WinAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseRoundDetails)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(12))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteLongS(constant.KeyBetId, betId)
	xw.WriteDoubleS(constant.KeyBetAmount, betAmt)
	xw.WriteDoubleS(constant.KeyWinAmount, winAmt)
	xw.WriteLongS(constant.KeyTimeRound, ret.BetTime)
	xw.WriteDoubleS(constant.KeyPayout, diceBet.Multiplier)
	xw.WriteStringS(constant.KeyClientSeed, diceBet.ClientSeed)
	xw.WriteStringS(constant.KeyCurrency, ret.Curr)
	xw.WriteBoolS(constant.KeyIsFreeBetGame, ret.FreeBet)
	xw.WriteBoolS(constant.KeyWin, response.IfWin(ret.Win))
	xw.WriteStringS(constant.KeyServerSeed, diceBet.ServerSeed)

	xw.WriteSfsObjectPre(constant.KeyDetails, uint16(3))
	xw.WriteDoubleS(constant.KeyResult, diceBet.Result)
	xw.WriteDoubleS(constant.KeyTargetAmount, diceBet.Chance)
	xw.WriteStringS(constant.KeyTargetType, diceBet.BetType)

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}
